概论
shell 是我们通过命令行和操作系统沟通的语言
shell 脚本可以在命令行中执行,shell 是一种解释性语言,不需要编译。
常见的 shell 有:
bash(默认)
zsh
fish
shell 文件开头需要指明 shell 解释器,如果是 bash,就写 #! /bin/bash
运行方式
- 解释器执行:不需要加权限,
bash test.sh
- 可执行文件运行:
- 添加可执行权限:
chmod +x test.sh
- 执行:
./test.sh
- 添加可执行权限:
注释
单行注释:#
多行注释
1 | :<<EOF |
其中EOF可以换成其它任意字符串。例如:abc / !
变量
定义变量
定义变量不需要加 $,变量默认都是字符串,默认值是空串 ""
,如果用到数字会自动转换。
注意:**= 前后不要加空格,平时写代码加空格习惯了,这里要注意了!!!**
1 | name1=tonngw # 不加引号 |
只读变量
使用 readonly
或者 declare
将变量变为只读
1 | name=tonngw |
删除变量
unset
删除变量
1 | name=tonngw |
变量类型
- 自定义变量(局部变量),子进程(比如新开一个 bash,退出 exit 或 Ctrl + d)不能访问
- 环境变量(全局变量),所有进程进行都可以访问
自定义变量改成环境变量
1 | $ name=tonngw # 定义变量 |
环境变量改为自定义变量
1 | export name=tonngw # 定义环境变量 |
字符串
字符串可以用 ''
、""
或者不用引号定义
单引号和双引号的区别:
- 单引号里的内容会原样输出
- 双引号里的内容可以转义、取变量、执行
1 | name=yxc # 不用引号 |
获取字符长度
1 | name=tonngw |
提取字串
1 | name="hello, world" |
默认变量
文件参数变量
在执行 shell 脚本的时候可以传递参数,$1
是第一个参数,$2
是第二个参数,以此类推,$0
是当前文件的绝对路径名
创建脚本文件 test.sh
1 |
|
执行脚本
1 | acs@9e0ebfcd82d7:~$ chmod +x test.sh |
其他参数相关变量
参数 | 说明 |
---|---|
$# | 文件传入参数的个数 |
$* | 由所有参数构成的用空格隔开的字符串,如上例中值为”$1 $2 $3 $4” |
$@ | 每个参数分别用双引号括起来的字符串,如上例中值为”$1” “$2” “$3” “$4” |
$$ | 获取脚本进程 ID |
$? | 获取上一条命令的退出状态,0 正常,其他错误 |
$(command) | 返回 command 的输出,可嵌套,比如:$(ls) |
command |
返回 command 的输出,不可嵌套 |
数组
数组可以存放多种不同类型的值(但本质上都是字符串),只支持一维数组,初始化时不需要指明大小,下标从 0 开始,可以跳着用下标,但实际占用的空间是有值的下标。
定义
法 1:小括号包围,内部元素用 空格 隔开
1 | array=(1 abc 'def' "ghi") |
法 2:用下标给数组赋值
1 | array[0]=1 |
获取数组某个元素的值
通过下标获取 ${array[index]}
1 | array=(1 abc 'def' "ghi") |
读取整个数组
1 | ${array[@]} # 法 1 |
例如:
1 | array=(1 abc 'def' "ghi") |
获取数组长度
类似于字符串获取长度,前面加一个 #
1 | ${#array[@]} # 法 1 |
expr 命令
expr 表达式
命令用于求表达式的值
使用说明:
- 用空格分隔每一项
- 特殊字符前用反斜杠
\
转义 - 对 包含空格和其他特殊字符 的字符串用引号括起来
- expr 会在
stdout
输出结果,如果是逻辑关系表达式,括号为真输出 1,否则输出 0(对比 ? 表达式) - expr 的
exit code
(返回值):如果是逻辑关系表达式,结果为真返回值为 0,否则为 1(对比 return 0 正常退出,其他非正常退出)
字符串表达式
length 字符串
,返回字符串的长度index 字符串 CHARSET
,返回在 CHARSET(字符集 abc)任意单个字符在字符串中最前面的字符位置,下标从 1 开始。如果字符串不存在 CHARSET 中的字符,则返回 0。substr 字符串 位置 长度
,返回字符串从指定位置开始固定长度的子串,如果位置或长度为负数、0 或非数值则返回空串。
示例:
1 | str="Hello World!" |
整数表达式
expr
支持普通的算术操作,算术表达式优先级低于字符串表达式,高于逻辑关系表达式。
+ -
:加减运算,两端参数会转换为整数,如果转换失败则报错。* / %
:乘除、取余运算,两端参数会转换为整数,如果转换失败则报错。()
:可以改变优先级,但需要用反斜杠转义
示例
1 | a=3 |
逻辑关系表达式
是短路运算符
|
类似于 C++ 的||
但返回值不一样如果参数 1 非空且非 0,则返回参数 1 的值,不会计算参数 2 的值。(短路)
否则如果返回参数 2 的值非空且非 0,则返回参数 2 的值,否则返回 0
&
类似于 C++ 的&&
但返回值不一样如果两个参数都非空且非 0,则返回参数 1 的值,不会计算参数 2 的值(短路),否则返回 0
<、<=、=、==、!=、>=、>
比较两端的参数,如果为true,则返回1,否则返回0。”==”是”=”的同义词。”expr”首先尝试将两端参数转换为整数,并做算术比较,如果转换失败,则按字符集排序规则做字符比较。
()
:可以改变优先级,但需要用反斜杠转义
示例
1 | a=3 |
read 命令
read
命令用于标准输入中读取单行数据。当读到文件结束符时,exit code
为 1,否则为 0。(类似于 C++ cin)
参数:
p
:后面可以接提示信息-t
:后面跟秒数 xs,指定接受用户输入的等待时间,超时自动结束
示例
1 | acs@9e0ebfcd82d7:~$ read name # 读入name的值 |
echo 命令
echo
用于输出字符串
echo STRING
显示普通字符串
1 | echo "Hello AC Terminal" |
显示转义字符
1 | echo "\"Hello AC Terminal\"" # 注意只能使用双引号,如果使用单引号,则不转义 |
显示变量
1 | name=tonngw |
显示换行
默认 echo 输出会换行,这里指的是两个换行
1 | echo -e "Hi\n" # -e 开启转义,输出换行 |
显示不换行
1 | echo -e "Hi \c" # -e 开启转义,\c 不换行,要放在双引号里面哦~ |
显示结果定向至文件
1 | echo "Hello World" > output.txt # 将内容以覆盖的方式输出到output.txt中 |
原样输出字符串,不进行转义或取变量(单引号)
1 | name=acwing |
显示命令的执行结果
1 | echo `date` |
printf 命令
printf
命令用于格式化输出,类似于 C/C++ 中的 printf 函数。
默认不会在字符串末尾添加换行符,区别于 echo 默认输出会换行
printf format-string [arguments...]
示例:
1 | printf "%10d.\n" 123 # 占10位,右对齐 |
test 命令和判断符号 []
test 和 [] 用法基本一样
逻辑运算符 && 和 ||
之前的 expr 里的逻辑关系运算符是 & 和 |,不一样哦
&&
表示与,||
表示或二者具有短路原则:(和 C++ 完全一样)
expr1 && expr2:当 expr1 为假时,直接忽略 expr2【通常会使用 && 链接执行多条命令】
expr1 || expr2:当 expr1 为真时,直接忽略 expr2表达式的 exit code 为0,表示真;为非零,表示假。(与C/C++中的定义相反)
test 命令
在命令行中输入 man test
,可以查看 test 命令的用法。
test 命令用于判断文件类型,以及对变量做比较。
test 命令用 exit code 返回结果,而不是使用 stdout。0表示真,非0表示假。
例如:
1 | test 2 -lt 3 # 为真,返回值为0 |
1 | acs@a7e3435d46dd:~$ ls |
文件类型判断
test -e filename
判断文件是否存在
-e
:判断文件是否存在-f
:判断是否为文件-d
:判断是否为目录
文件权限判断
test -r filename
判断文件是否可读
-r
:是否可读-w
:是否可写-x
:是否可执行-s
:是否为非空文件
整数比较
test $a -eq $b
a 是否等于 b
-eq
:是否等于-ne
:是否不等于-gt
:是否大于-lt
:是否小于-ge
:是否大于等于-le
:是否小于等于
字符串比较
test -z STRING
:判断 STRING 是否为空,如果为空,则返回 truetest -n STRING
:判断 STRING 是否非空,如果非空,则返回 true(-n 可以省略)test str1 == str2
:判断 str1 是否等于 str2test str1 != str2
:判断 str1 是否不等于 str2
多重条件判定
test -r filename -a -x filename
-a
:and,两条件是否同时成立-o
:or,两条件是否至少一个成立!
:取反,如 test ! -x file,当 file 不可执行时,返回 true
判断符号 []
[]
和 test
的用法几乎一致,常用于 if
语句中作为条件,另外 [[]] 是 [] 的加强版,支持的特性更多。
1 | [ 2 -lt 3 ] # 为真,返回值为0 |
1 | acs@9e0ebfcd82d7:~$ ls # 列出当前目录下的所有文件 |
注意:
[]
内的每一项需要用空格隔开[]
内的变量,最好用""
括起来[]
内的常数,最好用""
或者''
括起来
例如:
1 | name="acwing yxc" |
判断语句
if … then 形式
类似于 C/C++ 中的 if-else 语句。
单层 if
1 | if condition |
示例
1 | a=3 |
单层 if-else
1 | if condition |
多层 if-elif-elif-else
和 Python 类似
1 | if condition |
case … esac 形式
类似于 C/C++
中的 switch
语句。
1 | case $变量名称 in |
循环语句
do 和 done 可以看成是 { 和 }
for…in…do…done
类似于 C++ 的增强 for 循环
1 | for var in val1 val2 val3 |
示例 1,输出a 2 cc,每个元素一行:
1 | for i in a 2 cc |
示例 2,输出当前路径下所有文件名,每个文件名一行
1 | for file in `ls` |
示例 3,输出 1-10
1 | for i in $(seq 1 10) |
示例 4,使用{1..10} 或者 {a..z}
1 | for i in {a..z} |
for ((…;…;…)) do…done
类似于 C++ 的普通 for 循环
1 | for ((expression; condition; expression)) |
示例,输出1-10,每个数占一行:
1 | for ((i=1; i<=10; i++)) |
while…do…done 循环
类似于 C++ 的 while 循环
1 | while condition |
示例,**文件结束符为 Ctrl+d
**,输入文件结束符后 read 指令返回 false。
1 | while read name |
util…dp…done 循环
当 until 条件为真时结束,而 while 是条件为真时进入循环,和 while 相反
1 | until condition |
示例,当用户输入 yes 或者 YES 时结束,否则一直等待读入。
1 | until [ "${word}" == "yes" ] || [ "${word}" == "YES" ] |
break 命令
跳出每一层循环,和 C++ 不同的是:break
不能跳出 case 语句,而是用;;
跳出 case。
示例:
1 | while read name |
该示例每读入非EOF的字符串,会输出一遍1-7。
该程序可以输入 Ctrl+d
文件结束符来结束,也可以直接用 Ctrl+c
杀掉该进程。
continue 命令
跳出当前循环
示例:
1 | for ((i=1;i<=10;i++)) |
该程序输出1-10中的所有奇数。
死循环的处理方式
Ctrl + c
可以结束当前程序运行
否则可以通过进程号关闭进程
使用
top
命令找到要结束的进程 PID,按Ctrl + F
按内容从大到小排序(方便观察进程),或者ps aux | grep 要搜索的进程
输入
kill -9 PID
杀掉进程
函数
bash 中的函数类似于 C/C++ 中的函数,但 return 的返回值与 C/C++ 不同,返回的是exit code,取值为0-255,0表示正常结束。
如果想获取函数的输出结果,可以通过 echo 输出到 stdout 中,然后通过 $(func_name)
来获取 stdout 中的值。
1 | [function] func_name() { # function关键字可以省略 |
不获取 return 值和 stdout 值
1 | func() { |
获取 return 值和 stdout 值
不写 return 时,默认 return 0。
1 | func() { |
函数的输入参数
在函数内,$1
表示第一个输入参数,$2
表示第二个输入参数,依此类推。
注意:函数内的 $0
仍然是文件名,而不是函数名。
示例:递归求和 0-10
1 | func() { # 递归计算 $1 + ($1 - 1) + ($1 - 2) + ... + 0 |
函数内的局部变量
可以在函数内定义局部变量,作用范围仅在当前函数内。
可以在递归函数中定义局部变量。
local 变量名=变量值
示例
1 |
|
exit 命令
exit
命令用来退出当前 shell 进程,并返回一个退出状态,使用 $?
可以接收这个退出状态。
exit
命令可以接收一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。
exit
退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。
文件重定向
每个进程默认打开3个文件描述符:
stdin
标准输入,从命令行读取数据,文件描述符为 0stdout
标准输出,向命令行输出数据,文件描述符为 1stderr
标准错误输出,向命令行输出数据,文件描述符为2
重定向命令
command > file
:将stdout
重定向到file
中,即命令的结果输出到file
中,但每次执行会覆盖file
中的内容command < file
:将stdin
重定向到file
中,命令的输入来自file
command >> file
:将stdout
重定向到file
中,即命令的结果输出到 file 中,但每次执行追加file
中的内容command n> file
:将文件描述符n(0 1 2)
对应的文件重定向到file
中command n>> file
:将文件描述符n(0 1 2)
对应的文件以追加的方式重定向到file
中
示例:组合拳,test.sh 从 input.txt 中获取输入,输出到 output.txt
./test.sh < input.txt > output.txt
,或者 ./test.sh > output.txt < input.txt
引入外部脚本
类似于 C/C++ 中的 include 操作,bash
也可以引入其他脚本文件
1 | . filename # 注意点和文件名之间有一个空格 |
作业代码
评测作业的时候执行 ls -a
保证当前目录下只有脚本文件。
homework_0
进入 cd homework_0
创建 helper.sh 文件 touch helper.sh
编辑 helper.sh
脚本文件
最后把 helper.sh
的内容复制出来 :
- 先退出 tmux,
Ctrl + a
之后d
- 然后
cat helper.sh
- 按住
Shift
选中文件内容,Ctrl + Insert
复制,Shift + Insert
在 Linux/Windows 下粘贴,Windows 下也可以用Ctrl + v
粘贴。
1 |
|
homework_1
法 1:[ ]
1 |
|
法 2:test
1 |
|
homework_2
法 1:变量实现
1 |
|
法 2:数组实现
1 |
|
homework_3
main.sh
对照 C++ 代码实现
1 | #! /bin/bash |
C++ 代码
1 | #include <iostream> |
homework_4
1 |
|